﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using DotnetUtil.Json;
using DotnetUtil.Models;
using Flurl;
using Flurl.Http;

namespace DotnetUtil.Services
{
    public class HttpService : IHttpService
    {
        public async Task<Tuple<bool, string>> GetAsync(
            string url,
            Dictionary<string, object> queryParamDictionary,
            CancellationToken token = default
        )
        {
            url = AddQueryParam(url, queryParamDictionary);
            return await GetAsync(url, token);
        }

        public async Task<Tuple<bool, string>> GetAsync(string url, CancellationToken token = default)
        {
            try
            {
                var content = await url.GetStringAsync(HttpCompletionOption.ResponseContentRead, token);
                return new Tuple<bool, string>(true, content);
            }
            // ReSharper disable once IdentifierTypo
            catch (FlurlHttpException flurlHttpException)
            {
                var errorMessage = await GetErrorMessage(flurlHttpException);
                return new Tuple<bool, string>(false, errorMessage);
            }
            catch (Exception e)
            {
                return new Tuple<bool, string>(false, e.Message);
            }
        }

        public async Task<Tuple<bool, string>> PostAsync(
            string url, string body, CancellationToken token = default)
        {
            try
            {
                var httpContent = new StringContent(body, Encoding.UTF8, "application/json");
                var response = await url.PostAsync(httpContent, HttpCompletionOption.ResponseContentRead, token);
                if (response.StatusCode == StatusCodes.Status200OK ||
                    response.StatusCode == StatusCodes.Status201Created ||
                    response.StatusCode == StatusCodes.Status202Accepted ||
                    response.StatusCode == StatusCodes.Status204NoContent)
                {
                    var content = await response.ResponseMessage.Content.ReadAsStringAsync();
                    return new Tuple<bool, string>(true, content);
                }

                var errorMsg = await response.GetStringAsync();
                return new Tuple<bool, string>(false, errorMsg);
            }
            // ReSharper disable once IdentifierTypo
            catch (FlurlHttpException flurlHttpException)
            {
                var errorMessage = await GetErrorMessage(flurlHttpException);
                return new Tuple<bool, string>(false, errorMessage);
            }
            catch (Exception e)
            {
                return new Tuple<bool, string>(false, e.Message);
            }
        }

        public async Task<Tuple<bool, string>> DeleteAsync(string url)
        {
            try
            {
                var response = await url.DeleteAsync();
                if (response.StatusCode == StatusCodes.Status204NoContent)
                {
                    return new Tuple<bool, string>(true, string.Empty);
                }

                var errorMsg = await response.GetStringAsync();
                return new Tuple<bool, string>(false, errorMsg);
            }
            // ReSharper disable once IdentifierTypo
            catch (FlurlHttpException flurlHttpException)
            {
                var errorMessage = await GetErrorMessage(flurlHttpException);
                return new Tuple<bool, string>(false, errorMessage);
            }
            catch (Exception e)
            {
                return new Tuple<bool, string>(false, e.Message);
            }
        }

        public async Task<Tuple<bool, string>> PutAsync(string url, string body)
        {
            try
            {
                var httpContent = new StringContent(body, Encoding.UTF8, "application/json");
                var response = await url.PutAsync(httpContent);
                if (response.StatusCode == StatusCodes.Status200OK ||
                    response.StatusCode == StatusCodes.Status204NoContent)
                {
                    var content = await response.ResponseMessage.Content.ReadAsStringAsync();
                    return new Tuple<bool, string>(true, content);
                }

                var errorMsg = await response.GetStringAsync();
                return new Tuple<bool, string>(false, errorMsg);
            }
            // ReSharper disable once IdentifierTypo
            catch (FlurlHttpException flurlHttpException)
            {
                var errorMessage = await GetErrorMessage(flurlHttpException);
                return new Tuple<bool, string>(false, errorMessage);
            }
            catch (Exception e)
            {
                return new Tuple<bool, string>(false, e.Message);
            }
        }

        public async Task<Tuple<bool, string>> PatchAsync(string url, string body)
        {
            try
            {
                var httpContent = new StringContent(body, Encoding.UTF8, "application/json");
                var response = await url.PatchAsync(httpContent);
                if (response.StatusCode == StatusCodes.Status204NoContent)
                {
                    return new Tuple<bool, string>(true, string.Empty);
                }

                var errorMsg = await response.GetStringAsync();
                return new Tuple<bool, string>(false, errorMsg);
            }
            // ReSharper disable once IdentifierTypo
            catch (FlurlHttpException flurlHttpException)
            {
                var errorMessage = await GetErrorMessage(flurlHttpException);
                return new Tuple<bool, string>(false, errorMessage);
            }
            catch (Exception e)
            {
                return new Tuple<bool, string>(false, e.Message);
            }
        }

        #region GetAsync<T>

        public async Task<Tuple<bool, T, string>> GetAsync<T>(string url)
        {
            try
            {
                var content = await url.GetStringAsync();
                if (string.IsNullOrWhiteSpace(content))
                {
                    return new Tuple<bool, T, string>(true, default, string.Empty);
                }

                var objects = JsonHelper.DeserializeObject<T>(content);
                return new Tuple<bool, T, string>(true, objects, string.Empty);
            }
            // ReSharper disable once IdentifierTypo
            catch (FlurlHttpException flurlHttpException)
            {
                var errorMessage = await GetErrorMessage(flurlHttpException);
                return new Tuple<bool, T, string>(false, default, errorMessage);
            }
            catch (Exception e)
            {
                return new Tuple<bool, T, string>(false, default, e.Message);
            }
        }

        public async Task<Tuple<bool, T, string>> GetAsync<T>(
            string url, Dictionary<string, object> queryParamDictionary)
        {
            url = AddQueryParam(url, queryParamDictionary);
            return await GetAsync<T>(url);
        }

        #endregion

        #region 上传下载

        public async Task<Tuple<bool, string>> UploadFileAsync(string url, string fileName)
        {
            try
            {
                var httpContent = new MultipartFormDataContent();

                //添加文件
                var fileInfo = new FileInfo(fileName);

                httpContent.Add(new ByteArrayContent(
                        File.ReadAllBytes(fileName)),
                    "\"file\"", //该参数 form 中的name属性值,和 服务器端 必须对应起来
                    $"\"{fileInfo.Name}\"");

                var response = await url.PostAsync(httpContent);
                if (response.StatusCode == StatusCodes.Status200OK ||
                    response.StatusCode == StatusCodes.Status201Created ||
                    response.StatusCode == StatusCodes.Status202Accepted)
                {
                    var content = await response.ResponseMessage.Content.ReadAsStringAsync();
                    return new Tuple<bool, string>(true, content);
                }

                var errorMsg = await response.GetStringAsync();
                return new Tuple<bool, string>(false, errorMsg);
            }
            // ReSharper disable once IdentifierTypo
            catch (FlurlHttpException flurlHttpException)
            {
                var ret = flurlHttpException.StatusCode == StatusCodes.Status404NotFound;
                return new Tuple<bool, string>(ret, flurlHttpException.Message);
            }
            catch (Exception e)
            {
                return new Tuple<bool, string>(false, e.Message);
            }
        }

        public async Task<Tuple<bool, string>> DownloadFileAsync(string url, string fileName)
        {
            try
            {
                var stream = await url.GetStreamAsync();
                using (var fileStream = File.Create(fileName))
                {
                    await stream.CopyToAsync(fileStream);
                    await fileStream.FlushAsync();
                }

                return new Tuple<bool, string>(true, fileName);
            }
            // ReSharper disable once IdentifierTypo
            catch (FlurlHttpException flurlHttpException)
            {
                return new Tuple<bool, string>(false, flurlHttpException.Message);
            }
            catch (Exception e)
            {
                return new Tuple<bool, string>(false, e.Message);
            }
        }

        #endregion

        private static async Task<string> GetErrorMessage(FlurlHttpException exception)
        {
            var response = exception.Call?.Response;
            if (response == null)
            {
                return exception.Message;
            }

            var errorMessage = await response.GetStringAsync();
            if (string.IsNullOrWhiteSpace(errorMessage))
            {
                errorMessage = response.ResponseMessage.ReasonPhrase;
            }

            return errorMessage ?? string.Empty;
        }

        private static string AddQueryParam(string url, Dictionary<string, object> queryParamDictionary)
        {
            if (queryParamDictionary.Any())
            {
                foreach (var item in queryParamDictionary)
                {
                    var value = item.Value is DateTime time
                        ? time.ToString("yyyy-MM-ddTHH:mm:ssZ")
                        : item.Value;
                    url = url.SetQueryParam(item.Key, value);
                }
            }

            return url;
        }
    }
}
